#include <iostream>
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.
#include <math.h>
#include <sys/time.h>
#include <ImageMagick/Magick++.h>

using namespace std;

/* ascii code for the escape key */
#define ESCAPE 27

const int fps = 60;
const float tpf = 1.0 / 60;

/* The number of our GLUT window */
int window; 

GLfloat points[45][45][3];
int wiggle_count = 0;
GLfloat hold;


GLfloat     xrot;
GLfloat     yrot;
GLfloat     zrot;
GLuint      texture[1];

void LoadGLTextures()
{
	Magick::Image *pImage = NULL;
	Magick::Blob  blob;

	try {
		pImage = new Magick::Image("data/tim.bmp");
		pImage->write(&blob, "RGB");
	} catch (Magick::Error& error) {
		std::cerr << "加载纹理失败" << std::endl;
		exit(-1);
	}

    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);


    glTexImage2D(GL_TEXTURE_2D, 0, 3, pImage->columns(), pImage->rows(), 0, GL_RGB, GL_UNSIGNED_BYTE, blob.data());
	delete pImage;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
    LoadGLTextures();
    glEnable(GL_TEXTURE_2D);
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);		// This Will Clear The Background Color To Black
    glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LESS);				// The Type Of Depth Test To Do
    glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
    glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();				// Reset The Projection Matrix

    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

    glMatrixMode(GL_MODELVIEW);

    glPolygonMode(GL_BACK, GL_FILL);
    glPolygonMode(GL_FRONT, GL_LINE);

    int x, y;
    for(x=0; x<45; x++)
    {
        // Loop Through The Y Plane
        for(y=0; y<45; y++)
        {
            // Apply The Wave To Our Mesh
            points[x][y][0] = (float)((x/5.0f)-4.5f);
            points[x][y][1] = (float)((y/5.0f)-4.5f);
            points[x][y][2] = (float)(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
        }
    }
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
    if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
        Height=1;

    glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
}

struct timeval old, cur;

/* The main drawing function. */
void DrawGLScene()
{

    gettimeofday(&cur, 0);
    
    
    int x, y;
    float float_x, float_y, float_xb, float_yb;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
    glLoadIdentity();				// Reset The View

    glTranslatef(0.0f, 0.0f, -12.0f);
    glRotatef(xrot, 1.0f, 0.0f, 0.0f);
    glRotatef(yrot, 0.0f, 1.0f, 0.0f);
    glRotatef(zrot, 0.0f, 0.0f, 1.0f);

    glBindTexture(GL_TEXTURE_2D, texture[0]);

    //glPolygonMode(GL_BACK, GL_FILL);
    //glPolygonMode(GL_FRONT, GL_LINE);

    glBegin(GL_QUADS);

    for(x = 0; x < 44; ++x)
    {
        for(y = 0; y < 44; ++y)
        {
            float_x = (float)x / 44.0f;
            float_y = (float)y / 44.0f;
            float_xb = (float)(x + 1) / 44.0f;
            float_yb = (float)(y + 1) / 44.0f;

            glTexCoord2f(float_x, float_y);
            glVertex3f(points[x][y][0], points[x][y][1], points[x][y][2]);

            glTexCoord2f(float_x, float_yb);
            glVertex3f(points[x][y+1][0], points[x][y+1][1], points[x][y+1][2]);

            glTexCoord2f(float_xb, float_yb);
            glVertex3f(points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2]);

            glTexCoord2f(float_xb, float_y);
            glVertex3f(points[x+1][y][0], points[x+1][y][1], points[x+1][y][2]);
        }
    }

    glEnd();

    if (wiggle_count == 2)
    {
        for (y = 0; y < 45; ++y)
        {
            hold = points[0][y][2];
            for(x = 0; x < 44; ++x)
            {
                points[x][y][2] = points[x+1][y][2];
            }
            points[44][y][2] = hold;
        }
        wiggle_count = 0;
    }
    wiggle_count++;

    xrot += 0.3f;
    yrot += 0.2f;
    zrot += 0.4f;
    // since this is double buffered, swap the buffers to display what just got drawn.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
    /* avoid thrashing this procedure */
    usleep(100);

    /* If escape is pressed, kill everything. */
    if (key == ESCAPE) 
    { 
        /* shut down our window */
        glutDestroyWindow(window); 

        /* exit the program...normal termination. */
        exit(0);                   
    }
}

int main(int argc, char **argv) 
{  
    /* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
       X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
    glutInit(&argc, argv);  

    /* Select type of Display mode:   
       Double buffer 
       RGBA color
       Alpha components supported 
       Depth buffer */  
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

    /* get a 640 x 480 window */
    glutInitWindowSize(640, 480);  

    /* the window starts at the upper left corner of the screen */
    glutInitWindowPosition(0, 0);  

    /* Open a window */  
    window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");  

    /* Register the function to do all our OpenGL drawing. */
    glutDisplayFunc(&DrawGLScene);  

    /* Go fullscreen.  This is as soon as possible. */
    glutFullScreen();

    /* Even if there are no events, redraw our gl scene. */
    glutIdleFunc(&DrawGLScene);

    /* Register the function called when our window is resized. */
    glutReshapeFunc(&ReSizeGLScene);

    /* Register the function called when the keyboard is pressed. */
    glutKeyboardFunc(&keyPressed);

    /* Initialize our window. */
    InitGL(640, 480);

    /* Start Event Processing Engine */  
    glutMainLoop();  

    return 1;
}

